package controller

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"
	"webssh/src/core"
	"webssh/src/logger"
	"webssh/src/response"

	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"
)

// File 结构体
type File struct {
	Name       string `json:"name"`
	Size       string `json:"size"`
	ModifyTime string `json:"modifyTime"`
	Mode       string `json:"mode"`
	IsDir      bool   `json:"isDir"`
}

const (
	// BYTE 字节
	BYTE = 1 << (10 * iota)
	// KILOBYTE 千字节
	KILOBYTE
	// MEGABYTE 兆字节
	MEGABYTE
	// GIGABYTE 吉字节
	GIGABYTE
	// TERABYTE 太字节
	TERABYTE
	// PETABYTE 拍字节
	PETABYTE
	// EXABYTE 艾字节
	EXABYTE
)

// The unit that results in the smallest number greater than or equal to 1 is always chosen.
func Bytefmt(bytes uint64) string {
	unit := ""
	value := float64(bytes)

	switch {
	case bytes >= EXABYTE:
		unit = "E"
		value = value / EXABYTE
	case bytes >= PETABYTE:
		unit = "P"
		value = value / PETABYTE
	case bytes >= TERABYTE:
		unit = "T"
		value = value / TERABYTE
	case bytes >= GIGABYTE:
		unit = "G"
		value = value / GIGABYTE
	case bytes >= MEGABYTE:
		unit = "M"
		value = value / MEGABYTE
	case bytes >= KILOBYTE:
		unit = "K"
		value = value / KILOBYTE
	case bytes >= BYTE:
		unit = "B"
	case bytes == 0:
		return "0B"
	}

	result := strconv.FormatFloat(value, 'f', 2, 64)
	result = strings.TrimSuffix(result, ".00")
	return result + unit
}

type fileSplice []File

// Len 比较大小
func (f fileSplice) Len() int { return len(f) }

// Swap 交换
func (f fileSplice) Swap(i, j int) { f[i], f[j] = f[j], f[i] }

// Less 比大小
func (f fileSplice) Less(i, j int) bool { return f[i].IsDir }

// UploadFile 上传文件
func UploadFile(c *gin.Context) *response.ResponseBody {
	tenantId := c.GetString("tenantId")
	username := c.GetString("username")

	var (
		sshClient core.SSHClient
		err       error
	)

	id := c.PostForm("id")
	path := strings.TrimSpace(c.PostForm("path"))
	if id == "" {
		logger.Info("request id is empty")
		return response.ErrRequestParams
	}
	if path == "" {
		logger.Info("path is empty")
		return response.ErrEmptyFilePath
	}

	if sshClient, err = core.NewSSHClient(tenantId, username); err != nil {
		logger.Info(err.Error())
		return response.ErrCreateSSHClient
	}
	if err := sshClient.CreateSftp(); err != nil {
		logger.Info(err.Error())
		return response.ErrCreateSSHClient
	}
	defer sshClient.Close()
	file, header, err := c.Request.FormFile("file")
	if err != nil {
		logger.Info(err.Error())
		return response.ErrUploadFile
	}
	defer file.Close()

	pathArr := []string{strings.TrimRight(path, "/")}
	if dir := c.DefaultPostForm("dir", ""); dir != "" {
		pathArr = append(pathArr, dir)
		if err := sshClient.Mkdirs(strings.Join(pathArr, "/")); err != nil {
			logger.Info(err.Error())
			return response.ErrCreateFile
		}
	}
	pathArr = append(pathArr, header.Filename)
	err = sshClient.Upload(file, id, strings.Join(pathArr, "/"))
	if err != nil {
		logger.Info(err.Error())
		return response.ErrUploadFile
	}
	return response.SuccessResponse
}

// DownloadFile 下载文件
func DownloadFile(c *gin.Context) *response.ResponseBody {
	tenantId := c.GetString("tenantId")
	username := c.GetString("username")
	var (
		sshClient core.SSHClient
		err       error
	)

	path := strings.TrimSpace(c.DefaultQuery("path", "/root"))

	if sshClient, err = core.NewSSHClient(tenantId, username); err != nil {
		logger.Info(err.Error())
		return response.ErrCreateSSHClient
	}
	if err := sshClient.CreateSftp(); err != nil {
		logger.Info(err.Error())
		return response.ErrCreateSSHClient
	}
	defer sshClient.Close()
	if sftpFile, err := sshClient.Download(path); err != nil {
		logger.Info(err.Error())
		return response.ErrDownloadFile
	} else {
		defer sftpFile.Close()
		c.Writer.WriteHeader(http.StatusOK)
		fileMeta := strings.Split(path, "/")
		c.Header("Content-Disposition", "attachment; filename="+fileMeta[len(fileMeta)-1])
		_, _ = io.Copy(c.Writer, sftpFile)
	}
	return response.SuccessResponse
}

// UploadProgressWs 获取上传进度ws
func UploadProgress(c *gin.Context) *response.ResponseBody {
	wsConn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		logger.Info(err.Error())
		return response.ErrCreateWebsocket
	}
	id := c.Query("id")

	var ready, find bool
	for {
		if !ready && core.WcList == nil {
			continue
		}
		for _, v := range core.WcList {
			if v.Id == id {
				wsConn.WriteMessage(1, []byte(strconv.Itoa(v.Total)))
				find = true
				if !ready {
					ready = true
				}
				break
			}
		}
		if ready && !find {
			wsConn.Close()
			break
		}

		if ready {
			time.Sleep(300 * time.Millisecond)
			find = false
		}
	}
	return response.SuccessResponse
}

func tenantHomePath(tenantId string) string {
	return fmt.Sprintf("%s/%s", viper.GetString("server.ceph-rootpath"), tenantId)
}

func getUserHomeDir(tenantId, userName string) string {
	rootPath := viper.GetString("server.ceph-rootpath")
	userHomeSubpath := viper.GetString("server.ceph-userhome-subpath")
	return fmt.Sprintf("%s/%s/%s/%s", rootPath, tenantId, userHomeSubpath, userName)
}

func RemoveFile(c *gin.Context) *response.ResponseBody {
	tenantId := c.GetString("tenantId")
	username := c.GetString("username")

	path := c.Query("path")
	// 只能删除用户租户下的文件目录
	homePath := tenantHomePath(tenantId)
	if !strings.HasPrefix(path, homePath) {
		logger.Info("tenant %s user %s no permission for path %s", tenantId, username, path)
		return response.ErrAccessDeny
	}

	sshClient, err := core.NewSSHClient(tenantId, username)
	if err != nil {
		logger.Info(err.Error())
		return response.ErrCreateSSHClient
	}
	if err := sshClient.CreateSftp(); err != nil {
		logger.Info(err.Error())
		return response.ErrCreateSSHClient
	}
	defer sshClient.Close()
	if err := sshClient.Sftp.RemoveAll(path); err != nil {
		logger.Info("remove path %s err, %v", path, err.Error())
		return response.ErrDeleteFile
	}
	return response.SuccessResponse
}

func isValidFileName(name string) bool {
	if len(name) == 0 || len(name) > 200 {
		return false
	}
	pattern := "[^/,? ]"
	matched, err := regexp.MatchString(pattern, name)
	if err != nil {
		logger.Info("regexp err, %v", err.Error())
	}
	return matched
}

func Rename(c *gin.Context) *response.ResponseBody {
	tenantId := c.GetString("tenantId")
	username := c.GetString("username")

	path := c.Query("path")
	oldName := c.Query("oldName")
	newName := c.Query("newName")
	// 只能更新用户租户下的文件目录
	homePath := tenantHomePath(tenantId)
	if !strings.HasPrefix(path, homePath) {
		logger.Info("tenant %s user %s no permission for path %s", tenantId, username, path)
		return response.ErrAccessDeny
	}
	if oldName == "" {
		logger.Info("parameter oldName is empty")
		return response.ErrEmptyFilePath
	}
	if valid := isValidFileName(newName); !valid {
		return response.ErrInvalidFileName
	}

	oldFullName := filepath.Join(path, oldName)
	newFullName := filepath.Join(path, newName)

	sshClient, err := core.NewSSHClient(tenantId, username)
	if err != nil {
		logger.Info(err.Error())
		return response.ErrCreateSSHClient
	}
	if err := sshClient.CreateSftp(); err != nil {
		logger.Info(err.Error())
		return response.ErrCreateSSHClient
	}
	defer sshClient.Close()
	if err := sshClient.Sftp.Rename(oldFullName, newFullName); err != nil {
		logger.Info("remove path %s err, %v", path, err)
		return response.ErrRenameFile
	}
	return response.SuccessResponse
}

func CreateFile(c *gin.Context) *response.ResponseBody {
	tenantId := c.GetString("tenantId")
	username := c.GetString("username")

	path := c.Query("path")
	name := c.Query("name")
	// 只能更新用户租户下的文件目录
	homePath := tenantHomePath(tenantId)
	if !strings.HasPrefix(path, homePath) {
		logger.Info("tenant %s user %s no permission for path %s", tenantId, username, path)
		return response.ErrAccessDeny
	}
	if name == "" {
		logger.Info("parameter name is empty")
		return response.ErrInvalidFileName
	}
	if valid := isValidFileName(name); !valid {
		return response.ErrInvalidFileName
	}

	fullFileName := filepath.Join(path, name)

	sshClient, err := core.NewSSHClient(tenantId, username)
	if err != nil {
		logger.Info(err.Error())
		return response.ErrCreateSSHClient
	}
	if err := sshClient.CreateSftp(); err != nil {
		logger.Info(err.Error())
		return response.ErrCreateSSHClient
	}
	defer sshClient.Close()
	if _, err := sshClient.Sftp.Create(fullFileName); err != nil {
		logger.Info("create path %s err, %v", fullFileName, err)
		return response.ErrCreateFile
	}
	return response.SuccessResponse
}

func Mkdir(c *gin.Context) *response.ResponseBody {
	tenantId := c.GetString("tenantId")
	username := c.GetString("username")

	path := c.Query("path")
	dirName := c.Query("dirName")
	// 只能更新用户租户下的文件目录
	homePath := tenantHomePath(tenantId)
	if !strings.HasPrefix(path, homePath) {
		logger.Info("tenant %s user %s no permission for path %s", tenantId, username, path)
		return response.ErrAccessDeny
	}
	if dirName == "" {
		logger.Info("parameter name is empty")
		return response.ErrInvalidFileName
	}
	if valid := isValidFileName(dirName); !valid {
		logger.Info("dirName name is invalid")
		return response.ErrInvalidFileName
	}

	fullDirName := filepath.Join(path, dirName)

	sshClient, err := core.NewSSHClient(tenantId, username)
	if err != nil {
		logger.Info(err.Error())
		return response.ErrCreateSSHClient
	}
	if err := sshClient.CreateSftp(); err != nil {
		logger.Info(err.Error())
		return response.ErrCreateSSHClient
	}
	defer sshClient.Close()
	if err := sshClient.Sftp.Mkdir(fullDirName); err != nil {
		logger.Info("create path %s err, %v", fullDirName, err)
		return response.ErrCreateFile
	}
	return response.SuccessResponse
}

// FileContent 获取文件内容
func FileContent(c *gin.Context) *response.ResponseBody {
	tenantId := c.GetString("tenantId")
	username := c.GetString("username")
	filePath := c.Query("path")
	if filePath == "" {
		logger.Info("file path is empty")
		return response.ErrEmptyFilePath
	}
	homePath := tenantHomePath(tenantId)
	if !strings.HasPrefix(filePath, homePath) {
		logger.Info("tenant %s user %s no permission for path %s", tenantId, username, filePath)
		return response.ErrAccessDeny
	}
	bfile, err := readFile(filePath)
	if err != nil {
		logger.Info("read file content err, %v", err)
	}
	// 判断文件内容超过最大预览, 建议用户下载后查看
	if len(bfile) > viper.GetInt("filemanager.maxReadFileContent")*1024*1024 {
		logger.Info("file %s bytes %d over %dM", filePath, len(bfile), viper.GetInt("filemanager.maxReadFileContent"))
		return response.ErrReadLargeFile
	}
	var res = &response.ResponseBody{}
	res.Status = response.SuccessResponse.Status
	res.Data = string(bfile)
	return res
}

func readFile(filePath string) ([]byte, error) {
	bfile, err := os.ReadFile(filePath)
	if err != nil {
		return nil, err
	}
	return bfile, nil
}

// FileContent 更新文件
func UpdateFile(c *gin.Context) *response.ResponseBody {
	tenantId := c.GetString("tenantId")
	username := c.GetString("username")
	filePath := c.Query("path")
	if filePath == "" {
		logger.Info("file path is empty")
		return response.ErrEmptyFilePath
	}
	homePath := tenantHomePath(tenantId)
	if !strings.HasPrefix(filePath, homePath) {
		logger.Info("tenant %s user %s no permission for path %s", tenantId, username, filePath)
		return response.ErrAccessDeny
	}
	body, err := io.ReadAll(c.Request.Body)
	if err != nil {
		logger.Info("read request body err", err)
		return response.ErrReadFile
	}
	if err := overWriteFile(filePath, body); err != nil {
		logger.Info("write file err", err)
		return response.ErrWriteFile
	}
	return response.SuccessResponse
}

func overWriteFile(filePath string, content []byte) error {
	file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
	if err != nil {
		return err
	}
	defer file.Close()
	if _, err := file.Write(content); err != nil {
		return err
	}
	return nil
}

// FileList 获取文件列表
func FileList(c *gin.Context) *response.ResponseBody {
	tenantId := c.GetString("tenantId")
	username := c.GetString("username")

	path := c.Query("path")
	if path == "" {
		logger.Info("file path is empty")
		return response.ErrEmptyFilePath
	}
	// 只展示用户租户下的目录列表
	homePath := tenantHomePath(tenantId)
	if !strings.HasPrefix(path, homePath) {
		// 默认返回用户工作目录
		path = getUserHomeDir(tenantId, username)
	}

	sshClient, err := core.NewSSHClient(tenantId, username)
	if err != nil {
		logger.Info(err.Error())
		return response.ErrCreateSSHClient
	}
	if err := sshClient.CreateSftp(); err != nil {
		logger.Info(err.Error())
		return response.ErrCreateSSHClient
	}
	defer sshClient.Close()
	files, err := sshClient.Sftp.ReadDir(path)
	if err != nil {
		logger.Info("read path %s err, %s", path, err.Error())
		if strings.Contains(err.Error(), "exist") {
			return response.ErrFileOrDirNotExist
		} else {
			return response.ErrReadFile
		}
	}
	var (
		fileList fileSplice
		fileSize string
	)
	for _, mFile := range files {
		// 隐藏文件不展示
		if ignoreFile(mFile.Name()) {
			continue
		}
		if mFile.IsDir() {
			fileSize = strconv.FormatInt(mFile.Size(), 10)
		} else {
			fileSize = Bytefmt(uint64(mFile.Size()))
		}

		file := File{Name: mFile.Name(), IsDir: mFile.IsDir(), Mode: mFile.Mode().String(), Size: fileSize, ModifyTime: mFile.ModTime().Format("2006-01-02 15:04:05")}
		fileList = append(fileList, file)
	}
	sort.Stable(fileList)
	var res = &response.ResponseBody{}
	res.Status = response.SuccessResponse.Status
	res.Data = map[string]interface{}{
		"path": path,
		"list": fileList,
	}
	return res
}

func ignoreFile(name string) bool {
	// if name == ".ssh" || name == ".bash_history" || name == ".bash_logout" {
	// 	return true
	// }
	// 隐藏文件不展示
	return strings.HasPrefix(name, ".")
}
